﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.Contracts;

namespace GBricks.Collections
{
  partial class Comparers
  {
    public static EqualityComparer<T> ToTyped<T>(this IEqualityComparer<T> comparer) {
      Argument.NotNull(comparer, "comparer");
      Contract.Ensures(Contract.Result<EqualityComparer<T>>() != null);
      return comparer as EqualityComparer<T> ?? new TypedEqualityComparer<T>(comparer);
    }

    public static EqualityComparer<T> ToTyped<T>(this IEqualityComparer comparer) {
      Argument.NotNull(comparer, "comparer");
      Contract.Ensures(Contract.Result<EqualityComparer<T>>() != null);
      return comparer as EqualityComparer<T> ?? new UntypedEqualityComparer<T>(comparer);
    }

    public static Comparer<T> ToTyped<T>(this IComparer<T> comparer) {
      Argument.NotNull(comparer, "comparer");
      Contract.Ensures(Contract.Result<Comparer<T>>() != null);
      return comparer as Comparer<T> ?? new TypedComparer<T>(comparer);
    }

    public static Comparer<T> ToTyped<T>(this IComparer comparer) {
      Argument.NotNull(comparer, "comparer");
      Contract.Ensures(Contract.Result<Comparer<T>>() != null);
      return comparer as Comparer<T> ?? new UntypedComparer<T>(comparer);
    }

    [Serializable]
    private sealed class TypedEqualityComparer<T> : EqualityComparer<T>
    {
      public TypedEqualityComparer(IEqualityComparer<T> comparer) {
        Argument.NotNull(comparer, "comparer");
        Comparer = comparer;
      }

      private IEqualityComparer<T> Comparer { get; set; }

      [ContractInvariantMethod]
      private void Invariant() {
        Contract.Invariant(Comparer != null);
      }

      public override bool Equals(T x, T y) {
        return Comparer.Equals(x, y);
      }

      public override int GetHashCode(T obj) {
        return Comparer.GetHashCode(obj);
      }

      public override bool Equals(object obj) {
        var other = obj as TypedEqualityComparer<T>;
        return other != null && Equals(other.Comparer, Comparer);
      }

      public override int GetHashCode() {
        return Comparer.GetHashCode();
      }
    }

    [Serializable]
    private sealed class UntypedEqualityComparer<T> : EqualityComparer<T>
    {
      public UntypedEqualityComparer(IEqualityComparer comparer) {
        Argument.NotNull(comparer, "comparer");
        Comparer = comparer;
      }

      private IEqualityComparer Comparer { get; set; }

      [ContractInvariantMethod]
      private void Invariant() {
        Contract.Invariant(Comparer != null);
      }

      public override bool Equals(T x, T y) {
        return Comparer.Equals(x, y);
      }

      public override int GetHashCode(T obj) {
        Argument.NotNull(obj, "obj");
        return Comparer.GetHashCode(obj);
      }

      public override bool Equals(object obj) {
        var other = obj as UntypedEqualityComparer<T>;
        return other != null && Equals(other.Comparer, Comparer);
      }

      public override int GetHashCode() {
        return Comparer.GetHashCode();
      }
    }

    [Serializable]
    private sealed class TypedComparer<T> : Comparer<T>
    {
      public TypedComparer(IComparer<T> comparer) {
        Argument.NotNull(comparer, "comparer");
        Comparer = comparer;
      }

      private IComparer<T> Comparer { get; set; }

      [ContractInvariantMethod]
      private void Invariant() {
        Contract.Invariant(Comparer != null);
      }

      public override int Compare(T x, T y) {
        return Comparer.Compare(x, y);
      }

      public override bool Equals(object obj) {
        var other = obj as TypedComparer<T>;
        return other != null && Equals(other.Comparer, Comparer);
      }

      public override int GetHashCode() {
        return Comparer.GetHashCode();
      }
    }

    [Serializable]
    private sealed class UntypedComparer<T> : Comparer<T>
    {
      public UntypedComparer(IComparer comparer) {
        Argument.NotNull(comparer, "comparer");
        Comparer = comparer;
      }

      private IComparer Comparer { get; set; }

      [ContractInvariantMethod]
      private void Invariant() {
        Contract.Invariant(Comparer != null);
      }

      public override int Compare(T x, T y) {
        return Comparer.Compare(x, y);
      }

      public override bool Equals(object obj) {
        var other = obj as UntypedComparer<T>;
        return other != null && Equals(other.Comparer, Comparer);
      }

      public override int GetHashCode() {
        return Comparer.GetHashCode();
      }
    }
  }
}
